﻿#region Header

#region License
/*
Copyright (c) 2009, G.W. van der Vegt
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided 
that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the 
  following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 
  the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of G.W. van der Vegt nor the names of its contributors may be 
  used to endorse or promote products derived from this software without specific prior written 
  permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
*/
#endregion License

#region changelog

//----------   ---   -------------------------------------------------------------------------------
//Purpose:           IniFile Component with support for Comments.
//By:                G.W. van der Vegt (wvd_vegt@knoware.nl)
//Url:               http://swiss.codeplex.com
//Depends:           EncryptIt
//License:           New BSD License
//----------   ---   -------------------------------------------------------------------------------
//dd-mm-yyyy - who - description
//----------   ---   -------------------------------------------------------------------------------
//dd-mm-yyyy   veg - Reading an inifile (LoadFromFile).
//                 - Writing an inifile (SaveToFile).
//                 - As/ToString Property (Text).
//                 - Access Methods like ReadSections etc.
//                   Done for Integer, Bool and Double.
//                 - Added IsTested Method Attribute.
//                 - Added ReadSections, ReadSection and ReadSectionValues.
//                 - Added Clear override with Dirty Support.
//                 - Added Dirty Flag
//                   NOTE: Should be Tested Carefully!
//                 - Added support for Date, DateTime and Time.
//                 - Added Strutures.
//                 - Added ReadSection.
//                 - Added Documentation Skeletons.
//15-12-2008   veg - Added Structures.
//16-12-2008   veg - Added Encryption and Decryption.
//                 - Changed namespace into Swiss.
//11-02-2009   veg - Added IgnoreComment property.
//                 - Turned into IDisposable.
//27-11-2009   veg - Added UpdateFile()
//                 - Added FileName property.
//                 - Added constructor with FileName that calls LoadFile silently.
//02-12-2009   veg - Turned SaltAndKeys into a parameter of the Read/Write EncryptedString methods.
//                 - Removed own SaltAndKeys values out of EncryptIt!
//11-12-2009   veg - Corrected FileName behaviour of constructor. 
//                   If no path is passed, the file is created in a directory under %APPDATA%.
//                   The directory is either named after the Application Title or, 
//                   if absent, the Application Executable Name.
//                 - Added Read<T> and Write<T>.
//                 - Implemented RenameFile().
//                 - Added TestSuite to see wether we have a decent code coverage.
//12-12-2009   veg - Added Time/Date/DateTime detection to Write<DateTime>'s aValue.
//                 - Added Time/Date/DateTime detection to Read<DateTime>'s aDefaultValue.
//                 - Above two make sure the appropriate routine is chosen and unwante d
//                   parts of DateTime are suppressed. 
//                   NOTE: Read<DateTime> must pass a correctly ranged DefaultValue:
//                          for Read<'Time'> the Datepart should be 0 (ie DateTime.Date.Ticks==0),
//                          for Read<'Date'> the Timepart should be 0 (ie DateTime.Date.Ticks==DateTime.Ticks).
//                 - Added Read/Write<Structs> for Byte[], UInt16[] and UInt32[].
//                 - Added ReadChecksum() for above two routines.
//18-12-2009   veg - Replaced Assembly/Attribute code to get title by a much simpler Application.ProductName.
//             veg - Added safeguard for null values (translate to an empty string). 
//02-05-2010   veg - Changed behavior so empty or null strings will result in Application.ProductName.
//03-05-2010   veg - Extracted a number of Paths into static properties so the application can
//                   easily access them.
//                 - Changed meaning of AppData in _setfilename() to the subdirectory inside %APPDATA%.
//09-07-2010   veg - Added Transient Property and a new constructor to suppress Dirty bits warnings
//                   without additional coding.
//----------   ---   -------------------------------------------------------------------------------
//ddmmyyyy todo's
//                 * String to String, etc.
//                 * Render to Registry too.
//                 * Constructors like TMyIniFile.
//                 * Map to Property Space of a Project (xml?).
//                 - Perhaps Store Defaults there!
//                 - Support Empty Sections.
//                 - Use Events for DebugTraces.
//----------   ---   -------------------------------------------------------------------------------

#endregion changelog

#endregion Header

namespace Swiss
{
    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.Diagnostics;
    using System.IO;
    using System.Reflection;
    using System.Windows.Forms;

    /// <summary>
    /// Type of the IniFile.
    /// </summary>
    public enum IniFileType
    {
        /// <summary>
        /// The IniFile is a temporary one and will not be saved, 
        /// so a Dirty bit warning should be suppressed.
        /// </summary>
        Transient,

        /// <summary>
        /// The IniFile can be changed and should be saved, 
        /// so a Dirty bit warning should be given.
        /// </summary>
        Mutable
    }

    /// <summary cref="CollectionBase">
    /// The IniFile class, based on the CollectionBase Class
    /// </summary>
    public class IniFile : CollectionBase, IDisposable
    {
        #region Fields

        private Boolean fDirty;
        private String fFileName;

        #endregion Fields

        #region Constructors

        /// <summary cref="CommentPadding">
        /// The Constructor. 
        /// 
        /// Sets CommentPadding to the 72th Column.
        /// </summary>
        public IniFile()
        {
            CommentPadding = 72;
            IgnoreComment = false;
            Transient = false;
        }

        /// <summary cref="CommentPadding">
        /// The Constructor. 
        /// 
        /// Sets the Transient bit.
        /// </summary>
        public IniFile(IniFileType type)
            : this()
        {
            this.Transient = (type == IniFileType.Transient);
        }

        /// <summary cref="CommentPadding">
        /// The Constructor. 
        /// </summary>
        public IniFile(String IniFileName)
            : this()
        {
            LoadFromFile(IniFileName);
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// The Indent column of the Comment.
        /// </summary>
        public Int32 CommentPadding
        {
            get;
            set;
        }

        /// <summary>
        /// True if the IniFile is altered and needs to be saved.
        /// </summary>
        public Boolean Dirty
        {
            get
            {
                return fDirty;
            }
            set
            {
                fDirty = value;
            }
        }

        /// <summary>
        /// If set to true, comments are threated as part of the value.
        /// </summary>
        public Boolean IgnoreComment
        {
            get;
            set;
        }

        /// <summary>
        /// The Inifile as Text.
        /// </summary>
        [IsTested]
        public String Text
        {
            //TODO: Put getter and setter code into separate private routines
            [IsTested()]
            get
            {
                return _GetText();
            }
            [IsTested()]
            set
            {
                _SetText(value);
            }
        }

        /// <summary>
        /// Returns the filename passed to the constructor.
        /// 
        /// Note: '.\test.ini' is a special case (reads .{tab}est.ini!).
        ///       Correct is '.\\test.ini'.
        /// </summary>
        public String FileName
        {
            get { return fFileName; }
        }

        /// <summary>
        /// The Application's Directory
        /// </summary>
        public static String AppDir
        {
            get
            {
                return Path.GetDirectoryName(Application.ExecutablePath);
            }
        }

        /// <summary>
        /// The Default Application's IniFile Name
        /// </summary>
        public static String AppIni
        {
            get
            {
                return Application.ProductName + ".ini";
            }
        }

        /// <summary>
        /// The Application's AppData Directory. 
        /// 
        /// This directory is created when not already present.
        /// </summary>
        public static String AppData
        {
            get
            {
                String Result = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppTitle);

                if (!Directory.Exists(Result))
                {
                    Directory.CreateDirectory(Result);
                }

                return Result;
            }
        }

        /// <summary>
        /// The Application's My Documents Directory. 
        /// 
        /// This directory is created when not already present.
        /// </summary>
        public static String AppDoc
        {
            get
            {
                String Result = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), AppTitle);

                if (!Directory.Exists(Result))
                {
                    Directory.CreateDirectory(Result);
                }

                return Result;
            }
        }

        /// <summary>
        /// The Applications Title. 
        /// 
        /// It is either the ProductName or derived from the 
        /// last part of the Application's Directory.
        /// </summary>
        public static String AppTitle
        {
            get
            {
                if (String.IsNullOrEmpty(Application.ProductName))
                {
                    String[] split = AppDir.Split(Path.DirectorySeparatorChar);
                    return Path.ChangeExtension(split[split.Length - 1], "");
                }
                else
                {
                    return Application.ProductName;
                }
            }
        }

        /// <summary>
        /// Signals that the IniFile is a Transient one so saving is not important.
        /// </summary>
        public Boolean Transient
        {
            get;
            set;
        }

        #endregion Properties

        #region Indexers

        /// <summary cref="IniFileLine">
        /// The colltion of IniFileLines in this IniFile.
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        [IsTested]
        private IniFileLine this[Int32 index]
        {
            get
            {
                return ((IniFileLine)List[index]);
            }
            set
            {
                fDirty = true;
                List[index] = value;
            }
        }

        #endregion Indexers

        #region Methods

        /// <summary>
        /// Clear the IniFile.
        /// </summary>
        [IsTested]
        public new void Clear()
        {
            base.Clear();

            fDirty = true;
        }

        /// <summary>
        /// Delete a Key from the IniFile.
        /// </summary>
        /// <param name="aSection">The Section of the Key</param>
        /// <param name="aKey">The Key to remove</param>
        [IsTested]
        public void DeleteKey(String aSection, String aKey)
        {
            foreach (IniFileLine il in this)
            {
                if (il.Section.Equals(aSection, StringComparison.OrdinalIgnoreCase) &&
                    il.Key.Equals(aKey, StringComparison.OrdinalIgnoreCase))
                {
                    Remove(il);

                    return;
                }
            }
        }

        /// <summary>
        /// Simple Dispose only emitting a warning...
        /// 
        ///  But ignore the Dirty Bit if the IniFile is a Transient one.
        /// </summary>
        public void Dispose()
        {
            if (Dirty && !Transient)
            {
                Debug.WriteLine("Warning: IniFile modified but not saved!");
            }

            Text = "";
        }

        /// <summary>
        /// Erase a complete Section
        /// </summary>
        /// <param name="aSection">The Section to Remove</param>
        [IsTested]
        public void EraseSection(String aSection)
        {
            //Foreach won't work when deleting items during the enum...
            for (Int32 i = Count - 1; i >= 0; i--)
            {
                if (this[i].Section.Equals(aSection, StringComparison.OrdinalIgnoreCase))
                {
                    //The clean & simple way.
                    RemoveAt(i);

                    //This also works but leaves an extra section.
                    //this[i].Section = "";
                    //this[i].Key = "";
                    //this[i].Value = "";
                    //this[i].Comment = "";
                }
            }
        }

        /// <summary>
        /// IDisposable
        /// </summary>
        void IDisposable.Dispose()
        {
            Dispose();
        }

        /// <summary>
        /// Inserts am IniFileLine into the IniFile.
        /// </summary>
        /// <param name="index">The index  where to insert</param>
        /// <param name="value">The IniFileLine to insert</param>
        [IsTested]
        public void Insert(Int32 index, IniFileLine value)
        {
            fDirty = true;
            List.Insert(index, value);
        }

        /// <summary>
        /// Loads the IniCollection from a File.
        /// </summary>
        /// <param name="aFileName">the name of the file to load</param>
        [IsTested]
        public void LoadFromFile(String aFileName)
        {
            _SetFileName(aFileName);

            //Make sure the directory exists before attempting to access it.
            if (!Directory.Exists(Path.GetDirectoryName(fFileName)))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(fFileName));
            }

            //Make sure the file exists before attempting to read it.
            if (!File.Exists(fFileName))
            {
                FileStream myNewFile = File.Create(fFileName);
                myNewFile.Close();
            }

            using (StreamReader readFileStream = new StreamReader(fFileName))
            {
                Text = readFileStream.ReadToEnd();
            }

            fDirty = false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="aSection"></param>
        /// <param name="aKey"></param>
        /// <param name="aDefaultValue"></param>
        /// <returns></returns>
        public T Read<T>(String aSection, String aKey, T aDefaultValue)
        {
            Type mt = typeof(T);
            switch (mt.Name)
            {
                case "String":
                    return (T)(Object)ReadString(aSection, aKey, (String)(Object)aDefaultValue);
                case "Int32":
                    return (T)(Object)ReadInteger(aSection, aKey, (Int32)(Object)aDefaultValue);
                case "Byte":
                    return (T)(Object)ReadByte(aSection, aKey, (Byte)(Object)aDefaultValue);
                case "Boolean":
                    return (T)(Object)ReadBool(aSection, aKey, (Boolean)(Object)aDefaultValue);
                case "Double":
                    return (T)(Object)ReadDouble(aSection, aKey, (Double)(Object)aDefaultValue);
                case "DateTime":
                    DateTime value = (DateTime)(Object)aDefaultValue;
                    if (value.Date.Ticks == value.Ticks)
                    {
                        //No Time Part...
                        return (T)(Object)ReadDate(aSection, aKey, (DateTime)(Object)aDefaultValue);
                    }
                    else if (value.Date.Ticks == 0)
                    {
                        //No Date Part...
                        return (T)(Object)ReadTime(aSection, aKey, (DateTime)(Object)aDefaultValue);
                    }
                    else
                    {
                        //Date and Time Parts...
                        return (T)(Object)ReadDateTime(aSection, aKey, (DateTime)(Object)aDefaultValue);
                    }
                //Byte[] / Struct not supported as signature is different (returns checksum)
                //checksum has to be read separately.
                case "Byte[]":
                    {
                        Byte[] array = (Byte[])(Object)aDefaultValue;
                        Byte checksum = ReadStruct(aSection, aKey, ref array);
                        return (T)(Object)array;
                    }
                case "UInt16[]":
                    {
                        UInt16[] array = (UInt16[])(Object)aDefaultValue;
                        Byte checksum = ReadStruct(aSection, aKey, ref array);
                        return (T)(Object)array;
                    }
                case "UInt32[]":
                    {
                        UInt32[] array = (UInt32[])(Object)aDefaultValue;
                        Byte Checksum = ReadStruct(aSection, aKey, ref array);
                        return (T)(Object)array;
                    }
                default:
                    Debug.WriteLine(String.Format("Unsupported Type '{0}' for 'Read<T>'", mt.Name));
                    break;
            }
            return aDefaultValue;
        }

        /// <summary>
        /// Search for a Key and return its Boolean Value.
        /// 
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aDefaultValue">The Default value</param>
        /// <returns>Return the Default Value when it's not a valid Boolean</returns>
        [IsTested]
        public Boolean ReadBool(String aSection, String aKey, Boolean aDefaultValue)
        {
            Int32 result;
            if (!ValueExists(aSection, aKey) || !Int32.TryParse(ReadString(aSection, aKey, aDefaultValue.ToString()), out result))
            {
                return aDefaultValue;
            }
            return (result != 0);
        }

        /// <summary>
        /// Search for a Key and return its Comment if present. 
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <returns>The comment if any</returns>
        [IsTested]
        public String ReadComment(String aSection, String aKey)
        {
            foreach (IniFileLine il in this)
            {
                if (il.Section.Equals(aSection, StringComparison.OrdinalIgnoreCase) &&
                    il.Key.Equals(aKey, StringComparison.OrdinalIgnoreCase))
                {
                    return il.Comment;
                }
            }
            return "";
        }

        /// <summary>
        /// 
        /// </summary>
        /// <remarks>Reading a Date as a String may result in regional problems</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aDefault"></param>
        /// <returns></returns>
        [IsTested]
        public DateTime ReadDate(String aSection, String aKey, DateTime aDefault)
        {
            String s = ReadString(aSection, aKey, aDefault.ToString());

            DateTime Result = new DateTime();
            if (!ValueExists(aSection, aKey) || !DateTime.TryParse(s, out Result))
            {
                return aDefault.Date;
            }
            else
            {
                return Result.Date;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <remarks>Reading a DateTime as a String may result in regional problems</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aDefault"></param>
        /// <returns></returns>
        [IsTested]
        public DateTime ReadDateTime(String aSection, String aKey, DateTime aDefault)
        {
            String s = ReadString(aSection, aKey, aDefault.ToString());

            DateTime Result = new DateTime();
            if (!ValueExists(aSection, aKey) || !DateTime.TryParse(s, out Result))
            {
                return aDefault;
            }
            else
            {
                return Result;
            }
        }

        /// <summary>
        /// Search for a Key and return its Double Value.
        /// Return the Default Value when it's not a valid doublr.
        /// </summary>
        /// <remarks>Reading a floating point as a String may result in regional problems due to differences in Decimal Separator</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aDefaultValue"></param>
        /// <returns></returns>
        [IsTested]
        public Double ReadDouble(String aSection, String aKey, Double aDefaultValue)
        {
            Double result;
            if (!ValueExists(aSection, aKey) || !Double.TryParse(ReadString(aSection, aKey, aDefaultValue.ToString()), out result))
            {
                return aDefaultValue;
            }
            return result;
        }

        /// <summary>
        /// Reads and Encrypted String and Decrypts it.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aDefault">The Default (Unencrypted) Value</param>
        /// <param name="aSaltAndKeys">Salt and Keys for EncryptIt</param>
        /// <returns>The Decrypted String</returns>
        public String ReadEncryptedString(String aSection, String aKey, String aDefault, SaltAndKeys aSaltAndKeys)
        {
            if (ValueExists(aSection, aKey))
            {
                using (EncryptIt ei = new EncryptIt(aSaltAndKeys))
                {
                    return ei.Decrypt(ReadString(aSection, aKey, ""));
                }
            }
            else
            {
                return aDefault;
            }
        }

        /// <summary>
        /// Search for a Key and return its Int32 Value. 
        /// Return the Default Value when it's not a valid Int32.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aDefaultValue"></param>
        /// <returns></returns>
        [IsTested]
        public Int32 ReadInteger(String aSection, String aKey, Int32 aDefaultValue)
        {
            Int32 result;
            if (!ValueExists(aSection, aKey) || !Int32.TryParse(ReadString(aSection, aKey, aDefaultValue.ToString()), out result))
            {
                return aDefaultValue;
            }
            return result;
        }

        /// <summary>
        /// Search for a Key and return its Int32 Value. 
        /// Return the Default Value when it's not a valid Int32.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aDefaultValue"></param>
        /// <returns></returns>
        [IsTested]
        public Byte ReadByte(String aSection, String aKey, Byte aDefaultValue)
        {
            Int32 result;
            if (!ValueExists(aSection, aKey) || !Int32.TryParse(ReadString(aSection, aKey, aDefaultValue.ToString()), out result))
            {
                return aDefaultValue;
            }
            return (result >= 0 && result < 255) ? (Byte)result : aDefaultValue;
        }
        /// <summary>
        /// Read all key=value pairs from a section.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <returns>The key=value pairs</returns>
        [IsTested]
        public StringDictionary ReadSection(String aSection)
        {
            StringDictionary Result = new StringDictionary();
            foreach (IniFileLine il in this)
            {
                if (il.Section.Equals(aSection) && (il.Key.Trim().Length != 0))
                {
                    Result.Add(il.Key, il.Value);
                }
            }
            return Result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <returns></returns>
        [IsTested]
        public StringCollection ReadSectionValues(String aSection)
        {
            StringCollection Result = new StringCollection();
            foreach (IniFileLine il in this)
            {
                if (il.Section.Equals(aSection) && (il.Value.Trim().Length != 0))
                {
                    Result.Add(il.Value);
                }
            }
            return Result;
        }

        /// <summary>
        /// Read all section sames.
        /// </summary>
        /// <returns>The Section names</returns>
        [IsTested]
        public StringCollection ReadSections()
        {
            StringCollection Result = new StringCollection();
            foreach (IniFileLine il in this)
            {
                if (!Result.Contains(il.Section))
                {
                    Result.Add(il.Section);
                }
            }
            return Result;
        }

        /// <summary>
        /// Search for a Key and return its Value.
        /// Return the Default Value when the Key is not found.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aDefaultValue"></param>
        /// <returns></returns>
        [IsTested]
        public String ReadString(String aSection, String aKey, String aDefaultValue)
        {
            foreach (IniFileLine il in this)
            {
                if (il.Section.Equals(aSection, StringComparison.OrdinalIgnoreCase) &&
                    il.Key.Equals(aKey, StringComparison.OrdinalIgnoreCase))
                {
                    return il.Value;
                }
            }
            return aDefaultValue;
        }

        /// <summary>
        /// Reads a Struct and returns the Checksum.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <returns>The Checksum</returns>
        public byte ReadChecksum(String aSection, String aKey)
        {
            return CalcCheckSum(ReadString(aSection, aKey, ""));
        }

        /// <summary>
        /// Reads a Hex Structure into a Byte Array.
        /// </summary>
        /// <remarks>Calling CalcCheckSum is not very efficient but the code is always the same</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <returns>The checksum</returns>
        public byte ReadStruct(String aSection, String aKey, ref Byte[] aValue)
        {
            String hex = ReadString(aSection, aKey, "");

            Int32 bytesize = 2 * sizeof(Byte);

            if (hex.Length / bytesize != aValue.Length)
            {
                Debug.WriteLine(String.Format("Struct size mismatch {0} {1}", hex.Length / bytesize, aValue.Length));

                return 0;
            }
            else
            {
                for (Int32 i = 0; i < aValue.Length; i++)
                {
                    aValue[i] = Convert.ToByte(hex.Substring(i * bytesize, bytesize), 16);
                }

                return CalcCheckSum(hex);
            }
        }

        /// <summary>
        /// Reads a Hex Structure into a UInt16 Array.
        /// </summary>
        /// <remarks>Calling CalcCheckSum is not very efficient but the code is always the same</remarks>
        /// <remarks>There might be some Endian problems (Delphi Hottack32 data saved as '0908'
        /// is read back as 0x0908 instead of 0x0809!</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <returns>The checksum</returns>
        public byte ReadStruct(String aSection, String aKey, ref UInt16[] aValue)
        {
            String hex = ReadString(aSection, aKey, "");

            Int32 wordsize = 2 * sizeof(UInt16);

            if (hex.Length / wordsize != aValue.Length)
            {
                Debug.WriteLine(String.Format("Struct size mismatch {0} {1}", hex.Length / wordsize, aValue.Length));

                return 0;
            }
            else
            {
                for (Int32 i = 0; i < aValue.Length; i++)
                {
                    aValue[i] = Convert.ToUInt16(hex.Substring(i * wordsize, wordsize), 16);
                }

                return CalcCheckSum(hex);
            }
        }

        /// <summary>
        /// Reads a Hex Structure into a UInt32 Array.
        /// </summary>
        /// <remarks>Calling CalcCheckSum is not very efficient but the code is always the same</remarks>
        /// <remarks>There might be some Endian problems (Delphi Hottack32 data saved as '0908'
        /// is read back as 0x0908 instead of 0x0809!</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <returns>The checksum</returns>
        public byte ReadStruct(String aSection, String aKey, ref UInt32[] aValue)
        {
            String hex = ReadString(aSection, aKey, "");

            Int32 dwordsize = 2 * sizeof(UInt32);

            if (hex.Length / dwordsize != aValue.Length)
            {
                Debug.WriteLine(String.Format("Struct size mismatch {0} {1}", hex.Length / dwordsize, aValue.Length));

                return 0;
            }
            else
            {
                for (Int32 i = 0; i < aValue.Length; i++)
                {
                    aValue[i] = Convert.ToUInt32(hex.Substring(i * dwordsize, dwordsize), 16);
                }

                return CalcCheckSum(hex);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <remarks>Reading a Time as a String may result in regional problems</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aDefault"></param>
        /// <returns></returns>
        [IsTested]
        public DateTime ReadTime(String aSection, String aKey, DateTime aDefault)
        {
            String s = ReadString(aSection, aKey, aDefault.ToString());

            DateTime Result = new DateTime();
            if (DateTime.TryParse(s, out Result))
            {
                return new DateTime(0) + Result.TimeOfDay;
            }
            else
            {
                return aDefault;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="aFileName"></param>
        [IsTested]
        public void SaveToFile(String aFileName)
        {
            _SetFileName(aFileName);

            //Make sure the directory exists before attempting to access it.
            if (!Directory.Exists(Path.GetDirectoryName(fFileName)))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(fFileName));
            }

            using (StreamWriter writeFileStream = new StreamWriter(fFileName))
            {
                writeFileStream.Write(Text);
            }

            fDirty = false;
        }

        /// <summary>
        /// Renames an IniFile and removes the old one.
        /// </summary>
        /// <param name="aFileName">The new FileName</param>
        [IsTested]
        public void Rename(String aFileName)
        {
            String OldFile = FileName;

            SaveToFile(aFileName);

            if (File.Exists(OldFile))
            {
                File.Delete(OldFile);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="aSection"></param>
        /// <returns></returns>
        [IsTested]
        public Boolean SectionExists(String aSection)
        {
            foreach (IniFileLine il in this)
            {
                if (il.Section.Equals(aSection, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="tested"></param>
        public void TestStatus(Boolean tested)
        {
            Console.WriteLine("[{0}]", this.ToString());
            Console.WriteLine(";Start of class test status...");
            foreach (MethodInfo method in (typeof(IniFile)).GetMethods(/*BindingFlags.DeclaredOnly*/))
            {
                if (method.DeclaringType.Name.Equals("IniFile", StringComparison.OrdinalIgnoreCase) &&
                    IsMemberTested(method) == tested)
                {
                    if (IsMemberTested(method))
                    {
                        Console.WriteLine("{0}.{1}()=tested", method.DeclaringType.Name, method.Name);
                    }
                    else
                    {
                        Console.WriteLine("{0}.{1}()=untested", method.DeclaringType.Name, method.Name);
                    }
                }
            }
            Console.WriteLine(";...End of test status");
        }

        /// <summary>
        /// Updates the file on disk.
        /// </summary>
        public void UpdateFile()
        {
            SaveToFile(fFileName);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="aSection"></param>
        /// <param name="aKey"></param>
        /// <returns></returns>
        [IsTested]
        public Boolean ValueExists(String aSection, String aKey)
        {
            foreach (IniFileLine il in this)
            {
                if (il.Section.Equals(aSection, StringComparison.OrdinalIgnoreCase) &&
                    il.Key.Equals(aKey, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="aSection"></param>
        /// <param name="aKey"></param>
        /// <param name="aValue"></param>
        /// <returns></returns>
        public void Write<T>(String aSection, String aKey, T aValue)
        {
            Write<T>(aSection, aKey, aValue, String.Empty);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="aSection"></param>
        /// <param name="aKey"></param>
        /// <param name="aValue"></param>
        /// <param name="aComment"></param>
        /// <returns></returns>
        public void Write<T>(String aSection, String aKey, T aValue, String aComment)
        {
            Type mt = typeof(T);
            switch (mt.Name)
            {
                case "String":
                    WriteString(aSection, aKey, (String)(Object)aValue, aComment);
                    break;
                case "Int32":
                    WriteInteger(aSection, aKey, (Int32)(Object)aValue, aComment);
                    break;
                case "Byte":
                    WriteByte(aSection, aKey, (Byte)(Object)aValue, aComment);
                    break;
                case "Boolean":
                    WriteBool(aSection, aKey, (Boolean)(Object)aValue, aComment);
                    break;
                case "Double":
                    WriteDouble(aSection, aKey, (Double)(Object)aValue, aComment);
                    break;
                case "DateTime":
                    DateTime value = (DateTime)(Object)aValue;
                    if (value.Date.Ticks == value.Ticks)
                    {
                        //No Time Part...
                        WriteDate(aSection, aKey, (DateTime)(Object)aValue, aComment);
                    }
                    else if (value.Date.Ticks == 0)
                    {
                        //No Date Part...
                        WriteTime(aSection, aKey, (DateTime)(Object)aValue, aComment);
                    }
                    else
                    {
                        //Date and Time Parts...
                        WriteDateTime(aSection, aKey, (DateTime)(Object)aValue, aComment);
                    }
                    break;
                case "Byte[]":
                    WriteStruct(aSection, aKey, (Byte[])(Object)aValue, aComment);
                    break;
                case "UInt16[]":
                    WriteStruct(aSection, aKey, (UInt16[])(Object)aValue, aComment);
                    break;
                case "UInt32[]":
                    WriteStruct(aSection, aKey, (UInt32[])(Object)aValue, aComment);
                    break;
                default:
                    Debug.WriteLine(String.Format("Unsupported Type '{0}' for 'Write<T>'", mt.Name));
                    break;
            }
        }
        /// <summary>
        /// Write a Boolean as a String.
        /// </summary>
        /// <remarks>Writing a Boolean as a String may result in regional problems</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aComment">Optional Comment</param>
        [IsTested]
        public void WriteBool(String aSection, String aKey, Boolean aValue, String aComment)
        {
            WriteInteger(aSection, aKey, (aValue ? 1 : 0), aComment);
        }

        /// <summary>
        /// Write a Boolean as a String.
        /// </summary>
        /// <remarks>Writing a Boolean as a String may result in regional problems</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        [IsTested]
        public void WriteBool(String aSection, String aKey, Boolean aValue)
        {
            WriteBool(aSection, aKey, aValue, "");
        }

        /// <summary>
        /// Write the Date part of a DateTime as a String.
        /// </summary>
        /// <remarks>Writing a Date as a String may result in regional problems</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aComment">Optional Comment</param>
        [IsTested]
        public void WriteDate(String aSection, String aKey, DateTime aValue, String aComment)
        {
            WriteString(aSection, aKey, aValue.Date.ToString(), aComment);
        }

        /// <summary>
        /// Write the Date part of a DateTime as a String.
        /// </summary>
        /// <remarks>Writing a Date as a String may result in regional problems</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        [IsTested]
        public void WriteDate(String aSection, String aKey, DateTime aValue)
        {
            WriteDate(aSection, aKey, aValue, "");
        }

        /// <summary>
        /// Write the DateTime as a String.
        /// </summary>
        /// <remarks>Writing a DateTime as a String may result in regional problems</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aComment">Optional Comment</param>
        [IsTested]
        public void WriteDateTime(String aSection, String aKey, DateTime aValue, String aComment)
        {
            WriteString(aSection, aKey, aValue.ToString(), aComment);
        }

        /// <summary>
        /// Write the DateTime as a String.
        /// </summary>
        /// <remarks>Writing a DateTime as a String may result in regional problems</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        [IsTested]
        public void WriteDateTime(String aSection, String aKey, DateTime aValue)
        {
            WriteDateTime(aSection, aKey, aValue, "");
        }

        /// <summary>
        /// Write a Double as a String.
        /// </summary>
        /// <remarks>Writing a floating point as a String may result in regional problems due to differences in Decimal Separator</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aComment">Optional Comment</param>
        [IsTested]
        public void WriteDouble(String aSection, String aKey, Double aValue, String aComment)
        {
            WriteString(aSection, aKey, aValue.ToString(), aComment);
        }

        /// <summary>
        /// Write a Double as a String.
        /// </summary>
        /// <remarks>Writing a floating point as a String may result in regional problems due to differences in Decimal Separator</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        [IsTested]
        public void WriteDouble(String aSection, String aKey, Double aValue)
        {
            WriteDouble(aSection, aKey, aValue, "");
        }

        /// <summary>
        /// Encrypts a String and Writes it.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aComment">Optional Comment</param>
        /// <param name="aSaltAndKeys">Salt and Keys for EncryptIt</param>
        public void WriteEncryptedString(String aSection, String aKey, String aValue, String aComment, SaltAndKeys aSaltAndKeys)
        {
            using (EncryptIt ei = new EncryptIt(aSaltAndKeys))
            {
                WriteString(aSection, aKey, ei.Encrypt(aValue), aComment);
            }
        }

        /// <summary>
        /// Encrypts a String and Writes it.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aSaltAndKeys">Salt and Keys for EncryptIt</param>
        public void WriteEncryptedString(String aSection, String aKey, String aValue, SaltAndKeys aSaltAndKeys)
        {
            WriteEncryptedString(aSection, aKey, aValue, "", aSaltAndKeys);
        }

        /// <summary>
        /// Write an Integer value.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aComment">Optional Comment</param>
        [IsTested]
        public void WriteInteger(String aSection, String aKey, Int32 aValue, String aComment)
        {
            WriteString(aSection, aKey, aValue.ToString(), aComment);
        }

        /// <summary>
        /// Write an Integer value.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        [IsTested]
        public void WriteInteger(String aSection, String aKey, Int32 aValue)
        {
            WriteInteger(aSection, aKey, aValue, "");
        }

        /// <summary>
        /// Write an Byte value.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aComment">Optional Comment</param>
        [IsTested]
        public void WriteByte(String aSection, String aKey, Byte aValue, String aComment)
        {
            WriteString(aSection, aKey, aValue.ToString(), aComment);
        }

        /// <summary>
        /// Write an Byte value.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        [IsTested]
        public void WriteByte(String aSection, String aKey, Byte aValue)
        {
            WriteByte(aSection, aKey, aValue, "");
        }

        /// <summary>
        /// Write a String.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        [IsTested]
        public void WriteString(String aSection, String aKey, String aValue)
        {
            WriteString(aSection, aKey, aValue, "");
        }

        /// <summary>
        /// Write a String.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aComment">Optional Comment</param>
        [IsTested]
        public void WriteString(String aSection, String aKey, String aValue, String aComment)
        {
            Int32 aIndex = -1;
            foreach (IniFileLine il in this)
            {
                if (il.Section.Equals(aSection, StringComparison.OrdinalIgnoreCase))
                {
                    aIndex = this.IndexOf(il);

                    //Break if Key is also matching,
                    //but after replacing Value and Comment
                    if (il.Key.Equals(aKey, StringComparison.OrdinalIgnoreCase))
                    {
                        il.Value = aValue;
                        il.Comment = aComment;

                        fDirty = true;

                        return;
                    }
                }
            }

            if (aIndex == -1)
            {
                //Add a new Section & Key at the end of the Collection.
                Add(new IniFileLine(aSection, aKey, aValue, aComment));
            }
            else
            {
                //Insert a new Key at the end of the Section
                Insert(aIndex + 1, new IniFileLine(aSection, aKey, aValue, aComment));
            }
        }

        /// <summary>
        /// Writes a Byte Array as a Hex String.
        /// </summary>
        /// <remarks>Calling CalcCheckSum is not very efficient but the code is always the same</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aComment">Optional Comment</param>
        /// <returns>The checksum</returns>
        public byte WriteStruct(String aSection, String aKey, Byte[] aValue, String aComment)
        {
            Int32 bytesize = 2 * sizeof(Byte);

            String Result = String.Join(
                String.Empty,
                Array.ConvertAll(aValue, x => x.ToString(String.Format("X{0}", bytesize))));

            WriteString(aSection, aKey, Result, aComment);

            Debug.WriteLine(Result);

            return CalcCheckSum(Result);
        }

        /// <summary>
        /// Writes a Byte Array as a Hex String.
        /// </summary>
        /// <remarks>Calling CalcCheckSum is not very efficient but the code is always the same</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <returns>The checksum</returns>
        public byte WriteStruct(String aSection, String aKey, Byte[] aValue)
        {
            return WriteStruct(aSection, aKey, aValue, "");
        }

        /// <summary>
        /// Writes a UInt16 Array as a Hex String.
        /// </summary>
        /// <remarks>Calling CalcCheckSum is not very efficient but the code is always the same</remarks>
        /// <remarks>There might be some Endian problems (Delphi Hottack32 data saved as '0908'
        /// is read back as 0x0908 instead of 0x0809!</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aComment">Optional Comment</param>
        /// <returns>The checksum</returns>
        public byte WriteStruct(String aSection, String aKey, UInt16[] aValue, String aComment)
        {
            Int32 wordsize = 2 * sizeof(UInt16);

            String Result = String.Join(
                String.Empty,
                Array.ConvertAll(aValue, x => x.ToString(String.Format("X{0}", wordsize))));

            WriteString(aSection, aKey, Result, aComment);

            Debug.WriteLine(Result);

            return CalcCheckSum(Result);
        }

        /// <summary>
        /// Writes a UInt16 Array as a Hex String.
        /// </summary>
        /// <remarks>Calling CalcCheckSum is not very efficient but the code is always the same</remarks>
        /// <remarks>There might be some Endian problems (Delphi Hottack32 data saved as '0908'
        /// is read back as 0x0908 instead of 0x0809!</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <returns>The checksum</returns>
        public byte WriteStruct(String aSection, String aKey, UInt16[] aValue)
        {
            return WriteStruct(aSection, aKey, aValue, "");
        }

        /// <summary>
        /// Writes a UInt32 Array as a Hex String.
        /// </summary>
        /// <remarks>Calling CalcCheckSum is not very efficient but the code is always the same</remarks>
        /// <remarks>There might be some Endian problems (Delphi Hottack32 data saved as '0908'
        /// is read back as 0x0908 instead of 0x0809!</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aComment">Optional Comment</param>
        /// <returns>The checksum</returns>
        public byte WriteStruct(String aSection, String aKey, UInt32[] aValue, String aComment)
        {
            //StringBuilder hex = new StringBuilder(.
            Int32 dwordsize = 2 * sizeof(UInt32);

            String Result = String.Join(
                String.Empty,
                Array.ConvertAll(aValue, x => x.ToString(String.Format("X{0}", dwordsize))));

            WriteString(aSection, aKey, Result, aComment);

            Debug.WriteLine(Result);

            return CalcCheckSum(Result);
        }

        /// <summary>
        /// Writes a UInt32 Array as a Hex String.
        /// </summary>
        /// <remarks>There might be some Endian problems (Delphi Hottack32 data saved as '0908'
        /// is read back as 0x0908 instead of 0x0809!</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <returns>The checksum</returns>
        public byte WriteStruct(String aSection, String aKey, UInt32[] aValue)
        {
            return WriteStruct(aSection, aKey, aValue, "");
        }

        /// <summary>
        /// Write the Time part of a DateTime as a String.
        /// </summary>
        /// <remarks>Writing a Time as a String may result in regional problems</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aComment">Optional Comment</param>
        [IsTested]
        public void WriteTime(String aSection, String aKey, DateTime aValue, String aComment)
        {
            WriteString(aSection, aKey, aValue.TimeOfDay.ToString(), aComment);
        }

        /// <summary>
        /// Write the Time part of a DateTime as a String.
        /// </summary>
        /// <remarks>Writing a Time as a String may result in regional problems</remarks>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        [IsTested]
        public void WriteTime(String aSection, String aKey, DateTime aValue)
        {
            WriteTime(aSection, aKey, aValue, "");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="value"></param>
        protected override void OnInsert(Int32 index, Object value)
        {
            // Insert additional code to be run only when inserting values.
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="value"></param>
        protected override void OnRemove(Int32 index, Object value)
        {
            // Insert additional code to be run only when removing values.
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="oldValue"></param>
        /// <param name="newValue"></param>
        protected override void OnSet(Int32 index, Object oldValue, Object newValue)
        {
            // Insert additional code to be run only when setting values.
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        protected override void OnValidate(Object value)
        {
            if (value.GetType() != typeof(IniFileLine))
                throw new ArgumentException("value must be of type IniLine.", "value");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="member"></param>
        /// <returns></returns>
        [IsTested]
        private static Boolean IsMemberTested(MemberInfo member)
        {
            foreach (object attribute in member.GetCustomAttributes(true))
            {
                if (attribute is IsTestedAttribute)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Adds a new IniFileLine to the IniFile.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        [IsTested]
        private Int32 Add(IniFileLine value)
        {
            fDirty = true;
            return (List.Add(value));
        }

        /// <summary>
        /// Calculates a simple 8 bits checksum.
        /// </summary>
        /// <param name="aValue">The Hex String</param>
        /// <returns>The checksum</returns>
        private byte CalcCheckSum(String aValue)
        {
            Int32 bytesize = 2 * sizeof(Byte);

            byte chk = 0;

            for (Int32 i = 0; i < aValue.Length / bytesize; i++)
            {
                chk += Convert.ToByte(aValue.Substring(i * bytesize, bytesize), 16);
            }

            chk = (byte)(chk ^ 0xFF);

            return chk;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private Boolean Contains(IniFileLine value)
        {
            // If value is not of type Int16, this will return false.
            return (List.Contains(value));
        }

        /// <summary>
        /// Retrieves the Index of a IniFileLine.
        /// </summary>
        /// <param name="value">The IniFileLine of which to return the index</param>
        /// <returns>The Index of the IniFileLine value</returns>
        private Int32 IndexOf(IniFileLine value)
        {
            return (List.IndexOf(value));
        }

        /// <summary>
        /// Removes an IniFileLine from the IniFile.
        /// </summary>
        /// <param name="value"></param>
        private void Remove(IniFileLine value)
        {
            fDirty = true;
            List.Remove(value);
        }

        /// <summary>
        ///Sorting is still a bit of a problem.  
        ///It should only sort Sections and perhaps not the content of sections!
        /// </summary>
        private void Sort()
        {
            fDirty = true;

            IComparer Sorter = new SectionSorter();
            InnerList.Sort(Sorter);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        [IsTested]
        private String _GetText()
        {
            //TODO Create a good looking output including aligned comments
            //TODO Think about what to do when a value is empty (should we delete the key too?)

            //Missing cases are:
            //  key="" and non empty value.

            StringWriter sr = new StringWriter();

            String CurSection = "";
            foreach (IniFileLine il in this)
            {
                /*
                 * [current]
                 * velocity=high                   ;Current Velocity
                 * [default]
                 * =                               ;Lonely Comment
                 * baudrate=19200                  ;Baudrate of Serial Port
                 * partity=
                 * port=com1                       ;Serial Port Index
                 * stopbits=                       ;Lonely Key
                */
                if (il.Section.Length == 0)
                {
                    continue;
                }

                if (!CurSection.Equals(il.Section, StringComparison.OrdinalIgnoreCase))
                {
                    //Separate Sections
                    if (CurSection.Length != 0)
                    {
                        sr.WriteLine("");
                    }
                    sr.WriteLine("[{0}]", il.Section);
                    CurSection = il.Section;
                }
                if (il.Comment.Length == 0)
                {
                    if (il.Key.Length == 0)
                    {
                        if (il.Value.Length != 0)
                        {
                            sr.WriteLine(";Value only Line: '{0}'", il.Value); //DEBUG
                        }
                        else
                        {
                            sr.WriteLine(";Empty Line");                       //DEBUG
                        }
                    }
                    else
                    {
                        sr.WriteLine("{0}={1}", il.Key, il.Value);
                    }
                }
                else
                {
                    if (il.Key.Length == 0)
                    {
                        if (il.Value.Length != 0)
                        {
                            sr.WriteLine(";Value only Line with Comment: '{0}' ;{1}", il.Value, il.Comment); //DEBUG
                        }
                        else
                        {
                            sr.WriteLine("{0};{1}", il.Key.PadRight(CommentPadding), il.Comment);
                        }
                    }
                    else
                    {
                        sr.WriteLine("{0}={1};{2}", il.Key, il.Value.PadRight(CommentPadding - il.Key.Length - 1), il.Comment);
                    }
                }
            }
            return sr.ToString();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        [IsTested]
        private void _SetText(String value)
        {
            using (StringReader sr = new StringReader(value))
            {
                //Clear Current Data...
                //Clear sets Dirty.
                Clear();

                try
                {
                    String CurSection = "";
                    while (sr.Peek() > -1) //Returns -1 when it finds the end of file
                    {
                        String line = sr.ReadLine().Trim(); //Removes all spaces
                        String comment = "";

                        if (!IgnoreComment && line.Contains(";"))
                        {
                            comment = line.Substring(line.IndexOf(";") + 1).Trim();
                            line = line.Substring(0, line.IndexOf(";")).Trim();
                        }

                        if (line.Length == 0 & comment.Length != 0)
                        {
                            //No action if the Line is empty
                            Add(new IniFileLine(CurSection, "", "", comment));
                        }
                        else if (line.StartsWith("[") & line.EndsWith("]"))
                        {
                            CurSection = line.Substring(1, line.Length - 2);

                            //TODO: Support Empty Sections!

                            //Add(new IniFileLine(section,"","",""));
                        }
                        else if (line.Contains("="))
                        {
                            String Key = line.Substring(0, line.IndexOf("="));
                            String Value = line.Substring(line.IndexOf("=") + 1);
                            Add(new IniFileLine(CurSection, Key, Value, comment));
                        }
                    }
                }
                catch (System.Exception e)
                {
                    throw new System.Exception("Error parsing String", e);
                }
            }
        }

        internal void _SetFileName(String IniFileName)
        {
            String IniPath;

            if (String.IsNullOrEmpty(IniFileName))
            {
                IniPath = AppIni;
            }
            else
            {
                IniPath = IniFileName;
            }

            if (IniPath == Path.GetFileName(IniPath))
            {
                //No Path -> Prepend AppData (Vista/Windows 7 Compatible).
                fFileName = Path.Combine(AppData, IniPath);
            }
            else
            {
                //Path -> Honour it.
                fFileName = Path.GetFullPath(IniPath);
            }
        }

        #endregion Methods

        #region Nested Types

        private class SectionSorter : IComparer
        {
            #region Methods

            /// <summary>
            /// 
            /// </summary>
            /// <param name="x">The left value to compare</param>
            /// <param name="y">The right value to compare</param>
            /// <returns></returns>
            public Int32 Compare(Object x, Object y)
            {
                //TODO: Sort a bit more sophisticated than this, Section, then Key.
                //      Or even better, why sort at all (better loop when writing so
                //      all comments stay where they where...)

                IniFileLine c1 = (IniFileLine)x;
                IComparable ic1 = (IComparable)c1.Section;
                IniFileLine c2 = (IniFileLine)y;
                IComparable ic2 = (IComparable)c2.Section;

                return ic1.CompareTo(ic2);
            }

            #endregion Methods
        }

        #endregion Nested Types

        #region Other

        //TODO procedure Rename(const FileName: String; Reload: Boolean);

        #endregion Other
    }

    /// <summary>
    /// A line from an IniFile. Each line constists of a Section, Key, Value and Comment.
    /// Various combinations are possible (for instance empty Comment). 
    /// The Section is alway necessary
    /// </summary>
    public class IniFileLine
    {
        #region Constructors

        /// <summary>
        /// Create a new IniFileLine as IniFile.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aComment">Optional Comment</param>
        public IniFileLine(String aSection, String aKey, String aValue, String aComment)
        {
            //TODO: test and handle illegal Section==""
            Section = aSection.Trim();

            //TODO: test and handle illegal Key=="" and Value!="" pairs
            Key = aKey.Trim();
            Value = String.IsNullOrEmpty(aValue) ? String.Empty : aValue.Trim();

            Comment = aComment.Trim();
        }

        /// <summary>
        /// A Line from an IniFile.
        /// 
        /// The Value can be an Object.
        /// </summary>
        /// <param name="aSection">The Section</param>
        /// <param name="aKey">The Key</param>
        /// <param name="aValue">The Value</param>
        /// <param name="aComment">Optional Comment</param>
        public IniFileLine(String aSection, String aKey, object aValue, String aComment)
            : this(aSection, aKey, aValue.ToString(), aComment)
        {
            //Nothing
        }

        #endregion Constructors

        #region Properties

        /// <summary cref="IniFile.CommentPadding">
        /// Comment, indented to CommentPadding.
        /// </summary>
        public String Comment
        {
            get;
            set;
        }

        /// <summary cref="Value">
        /// The Key of a Key=Value pair.
        /// </summary>
        public String Key
        {
            get;
            set;
        }

        /// <summary>
        /// The Section of a IniFile Line.
        /// </summary>
        public String Section
        {
            get;
            set;
        }

        /// <summary cref="Key">
        /// The Value of a Key=Value pair.
        /// </summary>
        public String Value
        {
            get;
            set;
        }

        #endregion Properties
    }

    /// <summary>
    /// An Attribute used to signal if a member of a class is tested.
    /// </summary>
    class IsTestedAttribute : System.Attribute
    {
        #region Methods

        /// <summary>
        /// Returns the IsTestedAttribute as a String.
        /// </summary>
        /// <returns>Tested</returns>
        public override String ToString()
        {
            return "Tested";
        }

        #endregion Methods
    }
}
